#include <sys/types.h>
#include <sys/time.h>
#include <string.h>
#include <errno.h>
#include "kqueue_event.h"
#include "http_server.h"
#include "log.h"

int kqueue_create_event_manager (int size)
{
	int fd;

	if ((fd = kqueue ()) < 0) {
		if (http_server.log_fd != -1) {
			log_error ("kqueue: %s.", strerror (errno));
		}

		return -1;
	}

	return fd;
}

int kqueue_add_fd (int fd, int events)
{
	struct kevent ev;
	const struct timespec nullts = {0, 0};
	int filter;

	if (events & EVENT_IN) {
		filter = EVFILT_READ;
	} else {
		filter = EVFILT_WRITE;
	}

	EV_SET (&ev, fd, filter, EV_ADD | EV_CLEAR, 0, 0, 0);

	if (kevent (http_server.event_manager, &ev, 1, NULL, 0, &nullts) < 0) {
		if (http_server.log_fd != -1) {
			log_error ("kevent: %s.", strerror (errno));
		}

		return -1;
	}

	return 0;
}

int kqueue_del_fd (http_connection_t *http_connection)
{
	struct kevent ev;
	const struct timespec nullts = {0, 0};

	if (http_connection->filter == FILTER_READ) {
		EV_SET (&ev, http_connection->sd, EVFILT_READ, EV_DELETE, 0, 0, 0);
	} else {
		EV_SET (&ev, http_connection->sd, EVFILT_WRITE, EV_DELETE, 0, 0, 0);
	}

	if (kevent (http_server.event_manager, &ev, 1, NULL, 0, &nullts) < 0) {
		if (http_server.log_fd != -1) {
			log_error ("kevent: %s.", strerror (errno));
		}

		return -1;
	}

	return 0;
}

int kqueue_del_listener (int fd)
{
	struct kevent ev;
	const struct timespec nullts = {0, 0};

	EV_SET (&ev, fd, EVFILT_READ, EV_DELETE, 0, 0, 0);

	if (kevent (http_server.event_manager, &ev, 1, NULL, 0, &nullts) < 0) {
		if (http_server.log_fd != -1) {
			log_error ("kevent: %s.", strerror (errno));
		}

		return -1;
	}

	return 0;
}

int kqueue_event_wait (int timeout)
{
	struct timespec ts;
	struct timespec *pts;

	if (timeout < 0) {
		pts = NULL;
	} else {
		ts.tv_sec = timeout / 1000;
		ts.tv_nsec = (timeout % 1000) * 1000000;
		pts = &ts;
	}

	return kevent (http_server.event_manager, NULL, 0, http_server.events, http_server.max_file_descriptors, pts);
}

int GET_EVENT (event_t *event)
{
	int ret;

	switch (event->filter) {
		case EVFILT_READ:
			ret = EVENT_IN;
			break;
		case EVFILT_WRITE:
			ret = EVENT_OUT;
			break;
		default:
			ret = 0;
	}

	if (event->flags & EV_ERROR) {
		ret |= EVENT_ERR;
	}

	if ((event->flags & EV_EOF) && (ret != EVENT_IN)) {
		ret |= EVENT_HUP;
	}

	return ret;
}

int kqueue_prepare_for_reading (http_connection_t *http_connection)
{
	struct kevent ev[2];
	int nevents;
	const struct timespec nullts = {0, 0};

	EV_SET (&(ev[0]), http_connection->sd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, 0);

	if (http_connection->filter == FILTER_WRITE) {
		EV_SET (&(ev[1]), http_connection->sd, EVFILT_WRITE, EV_DELETE, 0, 0, 0);
		nevents = 2;
	} else {
		nevents = 1;
	}

	if (kevent (http_server.event_manager, ev, nevents, NULL, 0, &nullts) < 0) {
		if (http_server.log_fd != -1) {
			log_error ("kevent: %s.", strerror (errno));
		}

		return -1;
	}

	http_connection->filter = FILTER_READ;

	return 0;
}

int kqueue_prepare_for_writing (http_connection_t *http_connection)
{
	struct kevent ev[2];
	int nevents;
	const struct timespec nullts = {0, 0};

	EV_SET (&(ev[0]), http_connection->sd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, 0);

	if (http_connection->filter == FILTER_READ) {
		EV_SET (&(ev[1]), http_connection->sd, EVFILT_READ, EV_DELETE, 0, 0, 0);
		nevents = 2;
	} else {
		nevents = 1;
	}

	if (kevent (http_server.event_manager, ev, nevents, NULL, 0, &nullts) < 0) {
		if (http_server.log_fd != -1) {
			log_error ("kevent: %s.", strerror (errno));
		}

		return -1;
	}

	http_connection->filter = FILTER_WRITE;

	return 0;
}

int kqueue_remove_notifications (http_connection_t *http_connection)
{
	struct kevent ev;
	const struct timespec nullts = {0, 0};

	if (http_connection->filter == FILTER_READ) {
		EV_SET (&ev, http_connection->sd, EVFILT_READ, EV_DELETE, 0, 0, 0);
	} else {
		EV_SET (&ev, http_connection->sd, EVFILT_WRITE, EV_DELETE, 0, 0, 0);
	}

	if (kevent (http_server.event_manager, &ev, 1, NULL, 0, &nullts) < 0) {
		if (http_server.log_fd != -1) {
			log_error ("kevent: %s.", strerror (errno));
		}

		return -1;
	}

	http_connection->filter = NO_FILTER;

	return 0;
}
